﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace UnitTests
{
  public static class MyStringUtils
  {
    private static readonly List<KeyValuePair<Type, Func<object, string>>>
      Converters = new List<KeyValuePair<Type, Func<object, string>>>();

    private static readonly HashSet<PropertyInfo>
      Ignores = new HashSet<PropertyInfo>();

    public static void RegisterIgnore<T>(Expression<Func<T, object>> prop)
    {
      var memberExp = prop.Body as MemberExpression;

      if (memberExp == null)
      {
        // Check if it is an UnaryExpression and unwrap it
        var unaryExp = prop.Body as UnaryExpression;
        if (unaryExp != null)
          memberExp = unaryExp.Operand as MemberExpression;
      }

      if (memberExp == null)
        throw new ArgumentException("Cannot get name from expression", "expression");

      Ignores.Add((PropertyInfo) memberExp.Member);
    }

    public static void RegisterAsComplex<T>()
    {
      RegisterConverter<T>(t => t.MyWritePropertiesToString());
    }
    public static void RegisterConverter<T>(Func<T, string> converter)
    {
      Converters.Add(new KeyValuePair<Type, Func<object, string>>(typeof (T), x => converter((T)x)));
    }
    public static void RegisterConverter(Type t, Func<object, string> converter)
    {
      Converters.Add(new KeyValuePair<Type, Func<object, string>>(t, converter));
    }
    private static string Convert(object obj)
    {
      var genericArguments = obj.GetType().GetGenericArguments();
      foreach (var pair in Converters)
      {
        if (pair.Key.GetGenericArguments().Length != genericArguments.Length) continue;
        var genericType = pair.Key.IsGenericTypeDefinition 
          ? pair.Key.MakeGenericType(genericArguments) 
          : pair.Key;
        if (genericType.IsInstanceOfType(obj))
          return pair.Value(obj);
      }
      return obj.ToString();
    }

    public static string MyWritePropertiesToString<T>(this T value)
    {
      return WriteObjectToString(value, WriteProperties);
    }
    private static void WriteProperties<T>(T value, StringBuilder sb, Type t)
    {
      foreach (var propertyInfo in t.GetProperties())
      {
        if (!propertyInfo.CanRead) continue;
        if (Ignores.Contains(propertyInfo)) continue;
        var obj = propertyInfo.GetValue(value, new object[0]) ?? "NULL";
        sb.AppendFormat("\t{0}: {1}", propertyInfo.Name, Convert(obj)).AppendLine();
      }
    }
    private static string WriteObjectToString<T>(T value, Action<T, StringBuilder, Type> writer)
    {
      if (value == null) return string.Empty;
      var type = value.GetType();
      var stringBuilder = new StringBuilder();
      stringBuilder.AppendLine(type.Name);
      stringBuilder.AppendLine("{");
      writer(value, stringBuilder, type);
      stringBuilder.AppendLine("}");
      return stringBuilder.ToString();
    }

  }
}